﻿using System;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SignalLab.BaseLibrary;
using System.Collections.Generic;
using System.Diagnostics;

namespace SignalLab.Analysers
{
  public class BpmAnalyser : RealTimeAnalyserBase
  {
    IntegerRealtimeProperty AverageBPM;
    IntegerRealtimeProperty CurrentBPM;
    IntegerRealtimeProperty TotalBeats;

    PagedArray<byte> Raw;

    int rawDataBufferSize;

    /// <summary>
    /// hodnota, ktoru ked nejaka vzorka, zapocita sa uder
    /// </summary>
    public short BeatTopLimit { get; set; }
    
    /// <summary>
    /// ked sa dostanem pod tuto hodnotu, zacnem pocuvat dalsi uder
    /// </summary>
    public short BeatBottomLimit { get; set; }

    /// <summary>
    /// pozicie samplov, kedy sme prekrocili limit;
    /// </summary>
    public List<int> beatStarts = new List<int>();


    public BpmAnalyser(int sampleRate, int bitsPerSample, int rawDataBufferSize)
      : base(sampleRate, bitsPerSample)
    {
      this.rawDataBufferSize = rawDataBufferSize;
      Status = "waiting for signal";
      Raw = new PagedArray<byte>(rawDataBufferSize);
      AverageBPM = new IntegerRealtimeProperty("Average BPM");
      CurrentBPM = new IntegerRealtimeProperty("Current BPM");
      TotalBeats = new IntegerRealtimeProperty("TotalBeats");
      BeatTopLimit = (short)(short.MaxValue  * 0.8);
      BeatBottomLimit = (short)(BeatTopLimit * 0.9);
    }

    public override void Start()
    {
      i = 0;
      currentBeatStart = -1;
      analysing = false;
      beatStarts = new List<int>();
      Raw = new PagedArray<byte>(rawDataBufferSize);
      base.Start();
    }


    bool analysing = false;

    protected override void recieveData(byte[] pcmData)
    {
      Raw.AddPage(pcmData);
      if (!analysing) findBeats();
    }

    int i; //raw position
    int currentBeatStart = -1;

    int max = 0;

    private void findBeats()
    {
      analysing = true;
      int step = Helper.BYTES_PER_SAMPLE;
      if (Raw.Count <= step) return;

      int oldValue;
      int newValue = Helper.GetSampleAtPosition(Raw, i);

      for (; i < Raw.Count - step; i += step) 
      {
        oldValue = newValue;
        newValue = Math.Abs((int)Helper.GetSampleAtPosition(Raw, i + step));
        if (newValue > max) 
        {
          max = newValue;

        }
        if (newValue > BeatTopLimit && oldValue < BeatTopLimit && currentBeatStart < 0) //prekrocili sme horny limit zdola nahor
        {
          currentBeatStart = i / step; //prepneme sa do stavu beat
          beatStarts.Add(currentBeatStart);
          analyzeBeats();
        }
        else if (newValue < BeatBottomLimit && oldValue > BeatBottomLimit && currentBeatStart > 0) //prekrocili sme dolny limit zhora nadol 
        {
          currentBeatStart = -1; //vypneme sa zo stavu beat
        }
      }
      analysing = false;
    }

    private void analyzeBeats()
    {
      TotalBeats.Value = beatStarts.Count;

      if (beatStarts.Count > 1)
      {
        //last beat
        double time = Math.Abs(beatStarts[beatStarts.Count - 1] - beatStarts[beatStarts.Count - 2]) / (double)SampleRate;
        double frequency = 1 / time;
        CurrentBPM.Value = (int)Math.Round(frequency * 60);

        //now average
        time =  Math.Abs(beatStarts[beatStarts.Count - 1] - beatStarts[0]) / (double)SampleRate;
        frequency = 1 / time * beatStarts.Count;
        AverageBPM.Value = (int)Math.Round(frequency * 60);
      }
    }

    public override List<RealtimePropertyBase> GetProperties()
    {
      return new List<RealtimePropertyBase>()
      {
        AverageBPM, CurrentBPM, TotalBeats
      };
    }
  }
}
